import amara
from xml_io import *
from globals import *
from SignalDispatcher import SignalDispatcher
#from Factory import *
#from AttributeTypes import *

from . import *

class Attribute(SignalDispatcher, XML_IO):
    '''Base for all attributes.
    If initialized directly, it functions as as global attribute.
    Events and xml creation/parsing is done through this class.
    
    :Parameters:
        'name' : default is empty
        'id' : Universal ID.  Default is None (which assigns a new uuid)
        'class_name' : class specification (in progress)
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the attribute.  default is None.
    '''
    _xml_root_name = 'GlobalAttribute'
    _xml_vars = ['name', 'class_name', 'id']
    def __init__(self, **kwargs):
        SignalDispatcher.__init__(self)
        self.xml_root_name = Attribute._xml_root_name
        if hasattr(self, 'xml_vars') is False:
            self.xml_vars = set()
        if hasattr(self, 'xml_child_classes') is False:
            self.xml_child_classes = set()
        for var in Attribute._xml_vars:
            self.xml_vars.add(var)
        
        kwargs.setdefault('from_xml', None)
        xml = kwargs.get('from_xml')
        if xml is not None:
            self._from_xml(xml)
        else:
            kwargs.setdefault('id', None)
            kwargs.setdefault('name', '')
            kwargs.setdefault('class_name', '')
            self.name = kwargs.get('name')
            self.class_name = kwargs.get('class_name')
            self.id = setID(kwargs.get('id'))

class DefinitionAttribute(Attribute):
    '''Attribute that is attached to a device definition.
    
    :Parameters:
        'chan_index' : int, default is 1
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the attribute.  default is None.
    '''
    _xml_root_name = 'DefinitionAttribute'
    _xml_vars = ['chan_index']
    def __init__(self, **kwargs):
        if hasattr(self, 'xml_vars') is False:
            self.xml_vars = set()
        for var in DefinitionAttribute._xml_vars:
            self.xml_vars.add(var)
        Attribute.__init__(self, **kwargs)
        self.xml_root_name = DefinitionAttribute._xml_root_name
        
        kwargs.setdefault('from_xml', None)
        if kwargs.get('from_xml') is None:
            kwargs.setdefault('chan_index', 1)
            self.chan_index = kwargs.get('chan_index')

class PatchedAttribute(DefinitionAttribute):
    '''Attribute that is attached to a patched device.
    
    :Parameters:
        'parent_device_id' : id of the patched device this is attached to.
            default is None
        'universe_id' : DMX universe this attribute is patched to.
        'chan' : DMX channel the attribute is patched to.
        'value' : int, raw DMX value.  default is 0
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the attribute.  default is None.
    
    :Events:
        value_changed(attribute, value)
    '''
    
    _xml_root_name = 'PatchedAttribute'
    _xml_vars = ['universe_id', 'chan', 'parent_device_id', 'value', 'DefinitionAttribute_id']
    
    def __init__(self, **kwargs):
        if hasattr(self, 'xml_vars') is False:
            self.xml_vars = set()
        for var in PatchedAttribute._xml_vars:
            self.xml_vars.add(var)
        DefinitionAttribute.__init__(self, **kwargs)
        self.xml_root_name = PatchedAttribute._xml_root_name
        
        kwargs.setdefault('from_xml', None)
        if kwargs.get('from_xml') is None:
            kwargs.setdefault('parent_device_id', None)
            #kwargs.setdefault('patch_data', None)
            kwargs.setdefault('universe_id', None)
            kwargs.setdefault('chan', None)
            kwargs.setdefault('value', 0)
            self.DefinitionAttribute = kwargs.get('_DefinitionAttribute_')
            self.DefinitionAttribute_id = self.DefinitionAttribute.id
            self.parent_device_id = kwargs.get('parent_device_id')
            #self.patch_data = kwargs.get('patch_data')
            self.universe_id = kwargs.get('universe_id')
            self.chan = kwargs.get('chan')
            self.value = kwargs.get('value')
        self.register_signal('value_changed')
        
    def set_value(self, value):
        '''Use to set value of the attribute.  if listening,
            the appropriate universe will receive the value_changed signal
            and handle DMX transmission.
        '''
        
        self.value = value
        self.emit('value_changed', attribute=self, value=value)
        
    def on_value_changed(self, **kwargs):
        self.value = value
    
class IndexedAttribute(PatchedAttribute):
    '''An attribute that handles values in ranges
        (control, gobo/litho, effects wheels, etc.)
    
    :Parameters:
        'parameters' : list of parameters to be added.  elements in the list
            will be the same as kwargs for the add_parameter method.
            ([{name:'param1',val_min:0,val_max:10},{name:'param2',val_min:11,val_max:20}])
            default is None.
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the attribute.  default is None.
    
    :Events:
        'parameter_changed' (attribute, name)
            name is the name of the new active parameter,
            or None if nothing is active.
    '''
    
    _xml_root_name = 'IndexedAttribute'
    _xml_child_classes = ['parameters']
    def __init__(self, **kwargs):
        if hasattr(self, 'xml_vars') is False:
            self.xml_vars = set()
        if hasattr(self, 'xml_child_classes') is False:
            self.xml_child_classes = set()
        for cls in IndexedAttribute._xml_child_classes:
            self.xml_child_classes.add(cls)
        PatchedAttribute.__init__(self, **kwargs)
        self.xml_root_name = IndexedAttribute._xml_root_name
        
        kwargs.setdefault('from_xml', None)
        if kwargs.get('from_xml') is None:
            kwargs.setdefault('parameters', None)
            params = kwargs.get('parameters')
            self.parameters = {}
            if params is not None:
                for param in params:
                    self.add_parameter(val)
        self._find_active_parameter()
        self.register_signal('parameter_changed')
    def _make_child_from_xml(self, xml):
        doc = amara.parse(xml)
        xmlRoot = doc[doc.xml_properties.keys()[0]]
        new_kwargs = {}
        if hasattr(self, 'parameters') is False:
            self.parameters = {}
        if xmlRoot.type == IndexedParameter.xml_root_name:
            for key, val in xmlRoot.xml_child_elements.iteritems():
                if val.type in TYPE_STR_MAP:
                    valtype = TYPE_STR_MAP[val.type]
                    new_kwargs.update({str(key):valtype(val)})
            self.add_parameter(**new_kwargs)
    def add_parameter(self, **kwargs):
        '''Add a parameter index
        :Parameters:
            'name' : str, the name visible in the UI when the new parameter is active.
                also used as a dictionary key.
            'val_min' : int, minimum value for the new parameter to be active.
            'val_max' : int, maximum value for the new parameter to be active.            
        '''
        
        self.parameters.update({kwargs.get('name'):IndexedParameter(**kwargs)})
    def set_value(self, value):
        PatchedAttribute.set_value(self, value)
        self._find_active_parameter()
    def set_parameter(self, name):
        if name in self.parameters:
            self.set_value(self.parameters[name].val_min)
    def _find_active_parameter(self):
        active_index = None
        for key, val in self.parameters.iteritems():
            if val.is_active(self.value):
                active_index = key
        if active_index != self.active_index:
            self.active_index = active_index
            self.emit('parameter_changed', attribute=self, name=active_index)

class IndexedParameter(object):
    _xml_root_name = 'IndexedParameter'
    _xml_vars = ['name', 'val_min', 'val_max']
    def __init__(self, **kwargs):
        self.xml_root_name = self._xml_root_name
        self.xml_vars = self._xml_vars
        kwargs.setdefault('name', '')
        kwargs.setdefault('val_min', 0)
        kwargs.setdefault('val_max', 1)
        self.name = kwargs.get('name')
        self.val_min = int(kwargs.get('val_min'))
        self.val_max = int(kwargs.get('val_max'))
    def is_active(self, value):
        if value >= self.val_min and value <= self.val_max:
            return True
    def to_xml(self, **kwargs):
        kwargs.setdefault('plain_xml', False)
        doc = amara.create_document(u"root")
        name = unicode(self.xml_root_name)
        xmlRoot = doc.xml_create_element(name, attributes={u'type':name})
        for key in self.xml_vars:
            val = self.__dict__[key]
            t = str(type(val)).split(' ')[1]
            type_str = t[1:len(t)-2]
            xmlItem = doc.xml_create_element(unicode(key), attributes={u'type':unicode(type_str)})
            xmlItem.xml_append(unicode(val))
            xmlRoot.xml_append(xmlItem)
        
        if kwargs.get('plain_xml') is True:
            return xmlRoot.xml()
        else:
            return xmlRoot
